﻿using System;
using System.Collections.Generic;

namespace RiskCalc.Modelo
{
    public static class Calculos
    {
        public static double mediaAritmetica(List<double> vetValores)
        {
            int tamanho = vetValores.Count;

            double soma = 0;

            foreach (double dado in vetValores)
            {
                soma += dado;
            }

            return soma / tamanho;
        }

        public static double covariancia(List<double> vetRetornos1, double media1, List<double> vetRetornos2, double media2)
        {
            double soma = 0;

            for (int i = 0; i < vetRetornos1.Count; i++)
            {
                soma += (vetRetornos1[i] - media1) * (vetRetornos2[i] - media2);
            }

            return soma / vetRetornos1.Count;
        }

        public static double desvioPadrao(double variancia)
        {
            return Math.Sqrt(variancia);
        }

        public static double variancia(double media, List<double> vetValores)
        {
            double sum = 0;

            foreach (double valor in vetValores)
            {
                sum += (valor - media) * (valor - media);
            }

            return sum / vetValores.Count;
        }
        
        public static double[,] multiplicarMatrizes(int numLinhasM1, int numColunasM1, int numColunasM2, double[,] m1, double[,] m2)  
        {
            double[,] result = new double[numLinhasM1, numColunasM2];

            for (int i = 0; i < numLinhasM1; i++)
            {
                for (int j = 0; j < numColunasM2; j++)
                {
                    result[i, j] = 0;

                    for (int k = 0; k < numColunasM1; k++)
                    {
                        result[i, j] += m1[i, k] * m2[k, j];
                    }
                }
            }

            return result;
        }

        public static Double correlacao(double covariancia, double desvioPadrao1, double desvioPadrao2)
        {
            return covariancia / (desvioPadrao1 * desvioPadrao2);
        }

        public static bool decomposicaoCholesky(ref double[,] a, int n, bool isupper)
        {
            bool result = new bool();
            int i = 0;
            int j = 0;
            double ajj = 0;
            double v = 0;
            int i_ = 0;

            // Test the input parameters.
            System.Diagnostics.Debug.Assert(n>=0, "Error in SMatrixCholesky: incorrect function arguments");
            
            // Quick return if possible
            result = true;
            if( n<=0 )
            {
                return result;
            }
            if( isupper )
            {
                // Compute the Cholesky factorization A = U'*U.
                for(j=0; j<=n-1; j++)
                {
                    // Compute U(J,J) and test for non-positive-definiteness.
                    v = 0.0;
                    for(i_=0; i_<=j-1;i_++)
                    {
                        v += a[i_,j]*a[i_,j];
                    }
                    ajj = a[j,j]-v;
                    if( ajj<=0 )
                    {
                        result = false;
                        return result;
                    }
                    ajj = Math.Sqrt(ajj);
                    a[j,j] = ajj;
                    
                    // Compute elements J+1:N of row J.
                    if( j<n-1 )
                    {
                        for(i=j+1; i<=n-1; i++)
                        {
                            v = 0.0;
                            for(i_=0; i_<=j-1;i_++)
                            {
                                v += a[i_,i]*a[i_,j];
                            }
                            a[j,i] = a[j,i]-v;
                        }
                        v = 1/ajj;
                        for(i_=j+1; i_<=n-1;i_++)
                        {
                            a[j,i_] = v*a[j,i_];
                        }
                    }
                }
            }
            else
            {
                // Compute the Cholesky factorization A = L*L'.
                for(j=0; j<=n-1; j++)
                {
                    // Compute L(J,J) and test for non-positive-definiteness.
                    v = 0.0;
                    for(i_=0; i_<=j-1;i_++)
                    {
                        v += a[j,i_]*a[j,i_];
                    }
                    ajj = a[j,j]-v;
                    if( ajj<=0 )
                    {
                        result = false;
                        return result;
                    }
                    ajj = Math.Sqrt(ajj);
                    a[j,j] = ajj;
                    
                    // Compute elements J+1:N of column J.
                    if( j<n-1 )
                    {
                        for(i=j+1; i<=n-1; i++)
                        {
                            v = 0.0;
                            for(i_=0; i_<=j-1;i_++)
                            {
                                v += a[i,i_]*a[j,i_];
                            }
                            a[i,j] = a[i,j]-v;
                        }
                        v = 1/ajj;
                        for(i_=j+1; i_<=n-1;i_++)
                        {
                            a[i_,j] = v*a[i_,j];
                        }
                    }
                }
            }
            return result;
        }

        public static double inversaDistNormal(double p)
        {
            Double q;
            Double r;

            // Coefficients in rational approximations.
            const Double A1 = -39.696830286653757;
            const Double A2 = 220.9460984245205;
            const Double A3 = -275.92851044696869;
            const Double A4 = 138.357751867269;
            const Double A5 = -30.66479806614716;
            const Double A6 = 2.5066282774592392;

            const Double B1 = -54.476098798224058;
            const Double B2 = 161.58583685804089;
            const Double B3 = -155.69897985988661;
            const Double B4 = 66.80131188771972;
            const Double B5 = -13.280681552885721;

            const Double C1 = -0.0077848940024302926;
            const Double C2 = -0.32239645804113648;
            const Double C3 = -2.4007582771618381;
            const Double C4 = -2.5497325393437338;
            const Double C5 = 4.3746641414649678;
            const Double C6 = 2.9381639826987831;

            const Double D1 = 0.0077846957090414622;
            const Double D2 = 0.32246712907003983;
            const Double D3 = 2.445134137142996;
            const Double D4 = 3.7544086619074162;

            // Define break-points.
            const Double P_LOW = 0.02425;
            const Double P_HIGH = 1 - P_LOW;

            if (p > 0 && p < P_LOW) 
            {
                // Rational approximation for lower region.
                q = Math.Sqrt(-2 * Math.Log(p));

                return (((((C1 * q + C2) * q + C3) * q + C4) * q + C5) * q + C6) / 
                ((((D1 * q + D2) * q + D3) * q + D4) * q + 1);
            }
            else if (p >= P_LOW && p <= P_HIGH)
            {
                // Rational approximation for central region.
                q = p - 0.5;
                r = q * q;

                return (((((A1 * r + A2) * r + A3) * r + A4) * r + A5) * r + A6) * q / 
                (((((B1 * r + B2) * r + B3) * r + B4) * r + B5) * r + 1);
            }
            else if (p > P_HIGH && p < 1)
            {
                // Rational approximation for upper region.
                q = Math.Sqrt(-2 * Math.Log(1 - p));

                return -(((((C1 * q + C2) * q + C3) * q + C4) * q + C5) * q + C6) / 
                    ((((D1 * q + D2) * q + D3) * q + D4) * q + 1);
            }
            else
            {
                throw new ArgumentOutOfRangeException();
            }
        }

        public static double percentil(List<double> vetValores, int percentil)
        {
            // Ordena
            vetValores.Sort();

            double posicao = (vetValores.Count + 1) * (100 - percentil) / 100.0;
            double numeroEsquerda = 0.0d;
            double numeroDireita = 0.0d;

            double n = percentil / 100.0d * (vetValores.Count - 1) + 1.0d;

            if (posicao >= 1)
            {
                numeroEsquerda = vetValores[(int)System.Math.Floor(n) - 1];
                numeroDireita = vetValores[(int)System.Math.Floor(n)];
            }
            else
            {
                numeroEsquerda = vetValores[0];
                numeroDireita = vetValores[1];
            }

            if (numeroEsquerda == numeroDireita)
            {
                return numeroEsquerda;
            }
            else
            {
                double parte = n - System.Math.Floor(n);
                return numeroEsquerda + parte * (numeroDireita - numeroEsquerda);
            }
        }

        public static double perdaOuGanho(double posicao, double retorno)
        {
            return posicao * (Math.Exp(retorno) - 1);
        }

        public static double retornoGeometrico(double valorDiaAnterior, double valorDiaAtual)
        {
            //return Math.Log(valorDiaAtual / valorDiaAnterior);
            return Math.Log(valorDiaAtual / valorDiaAnterior);
        }

        public static double retornoSimulado(double retornoOriginal)
        {
            return Math.Exp(retornoOriginal) - 1;
        }

        public static double[,] transpor(int numLinhas, int numColunas, double[,] matriz)
        {
            double[,] result = new double[numColunas, numLinhas];

            for (int i = 0; i < numLinhas; i++)
            {
                for (int j = 0; j < numColunas; j++)
                {
                    result[j, i] = matriz[i, j];
                }
            }

            return result;
        }

        public static double varParametrico(double posicao, double desvioPadrao, double media, double confianca)
        {
            // Equação do VaR pelo retorno geométrico
            return posicao - (posicao *

              // "e" elevado a ()
              Math.Exp(

              // Integração da função densidade de probabilidade normal com média 0 e desvio padrão 1
              confianca * -1 *

               // Media dos Retornos Geométricos dos últimos <janelaDias> dias
              (desvioPadrao +

              // Desvio Padrão dos Retornos Geométricos dos últimos cdias
              media)));
        }

        public static double varParametricoCarteira(double[,] vetVaRs, double[,] matrizCorrelacao)
        {
            double[,] result = new double[matrizCorrelacao.Length, matrizCorrelacao.Length];

            int numVars = vetVaRs.Length;

            result = multiplicarMatrizes(1, numVars, 1,

                     multiplicarMatrizes(1, numVars, numVars, vetVaRs, matrizCorrelacao),

                        transpor(1, numVars, vetVaRs));

            return Math.Sqrt(result[0, 0]);
        }
    }
}